/*-
 * CSVReader.java     v2.0  25-Jan-2005
 *
 * $LastChangedBy: stein $
 * $LastChangedRevision: 42 $
 * $LastChangedDate: 2006-12-02 03:13:47 +0000 (Sat, 02 Dec 2006) $
 * $HeadURL: svn://localhost/SMS/src/no/sgb/io/CSVReader.java $
 *
 * Copyright (c) 2007, Stein Gunnar Bakkeby
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Stein Gunnar Bakkeby nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.attention.io;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * CSV Reader, reads comma separated values from an input stream.
 *
 * @author Stein Gunnar Bakkeby
 */
public class CSVReader {
  private String delim = ",";
  private int max_length = 512;
  private boolean labels = false;
  private String[][] csv;
  private InputStream stream;
  private boolean newStream = true;
  
  /**
   * Constructor, creates a default CSV Reader.
   * 
   * @see #setInputStream(InputStream)
   */
  public CSVReader() {
    newStream = false;
    csv = new String[0][0];
  }
  
  /**
   * Constructor, creates the CSV Reader.
   * 
   * @param stream The input stream to read from
   * @param firstLineLabels True if first row contains labels, false otherwise
   */
  public CSVReader(InputStream stream, boolean firstLineLabels) {
    this.stream = stream;
    this.labels = firstLineLabels;
  }
  
  /**
   * This will read the CSV data from the input stream.
   * Note that this method only works once as the stream is closed
   * when reading has ended. If a new input stream is set then
   * this method will automatically work again.
   * 
   * @see #setInputStream(InputStream)
   * @see #getCSV()
   */
  public void read() {
    if (!newStream) {
      return;
    }
    newStream = false;
    String[][] tmp = new String[0][0];
    int entriesNum = 0;
    /* Read entries from stream --> tmp */
    int columns = 0;
    try {
      BufferedReader reader = new BufferedReader(
          new InputStreamReader(stream));
      String line = reader.readLine();
      String[] v = line.split(delim);
      columns = v.length;
      tmp = new String[columns][max_length];
      int j;
      if (labels) {
        j = 0;
      } else {
        j = 1;
        for (int c = 0; c < columns; c++) {
          tmp[c][0] = v[c]; 
        }
        entriesNum++;
      }
      for (; line != null; j++) {
        line = reader.readLine();
        if (line == null) break;
        if (j >= max_length) { // double temporary storage
          String[][] tmp2 = new String[columns][max_length * 2];
          for (int c = 0; c < columns; c++) {
            for (int k = 0; k < max_length; k++) {
              tmp2[c][k] = tmp[c][k];
            }
          }
          max_length *= 2;
          tmp = tmp2;
        }
        if (!line.equals("")) { // skip empty lines
          String[] values = line.split(delim);
          if (values.length == columns) {
            for (int c = 0; c < columns; c++) {
              if (values[c].equals("" + (char) 0)) {
                values[c] = delim;
              }
              tmp[c][j] = values[c];              
            }
            entriesNum++;
          }
        }
      }
      reader.close();
    } catch (IOException e) {
      ;; System.out.println(e);
    }
    
    /* Remove unused space */
    String[][] tmp2 = new String[columns][entriesNum];
    for (int c = 0; c < columns; c++) {
      for (int e = 0; e < entriesNum; e++) {
        tmp2[c][e] = tmp[c][e];
      }
    }
    csv = tmp2;
  }
  
  /**
   * This will set the default buffer used when reading from the
   * input stream.
   * 
   * @param size Integer > 0
   */
  public void setBufferSize(final int size) {
    if (size > 0) this.max_length = size;
  }
  
  /**
   * This will set the input stream, use this to reuse the 
   * CSVReader object.
   * 
   * @param stream The stream to read from
   */
  public void setInputStream(final InputStream stream) {
    this.stream = stream;
    this.newStream = true;
  }
  
  /**
   * Use this to indicate whether the first line contains labels or not,
   * default value is false.
   * 
   * @param value True if first row contains lables, false otherwise
   */
  public void setFirstLineLabels(final boolean value) {
    labels = value;
  }
  
  /**
   * Use this to close the input stream.
   * 
   * @throws IOException If any occurs
   */
  public void close() throws IOException {
    stream.close();
  }
  
  /**
   * This will return the number of columns in the CSV data.
   * 
   * @return The number of columns
   */
  public int getNumberOfColumns() {
    return csv.length;
  }
  
  /**
   * This will return the number of rows in the CSV data.
   * 
   * @return Number of rows / entries
   */
  public int getNumberOfRows() {
    return csv[0].length;
  }
  
  /**
   * This will return the CSV data as a String[columns][rows].
   * The method should be used after readStream().
   * 
   * @return The CSV data
   * @see #getNumberOfColumns()
   * @see #getNumberOfRows()
   * @see #read()
   */
  public String[][] getCSV() {
    return csv;
  }
  
  /**
   * This will set the delimiter (which separates the values),
   * the default value is comma &quot;,&quot;
   * 
   * @param delim The delimiter
   */
  public void setDelimiter(final String delim) {
    this.delim = delim;
  }
  
  /**
   * This will sort the CSV data based on a given column.
   * 
   * @param column The column to sort by
   * @param ascending True if data should be ascending, false if descending
   */
  public void sortBy(final int column, final boolean ascending) {
    if (column < 0 || column >= csv.length) return;
    String[][] copy = csv; // faster working on a local copy
    int rows = copy[0].length;
    int columns = copy.length;
    if (ascending) {
      for (int i = 0; i < rows - 1; i++) {
        for (int j = i + 1; j < rows; j++) {
          if (copy[column][i].compareTo(copy[column][j]) > 0) {
            for (int k = 0; k < columns; k++) {
              String tmp = copy[k][i];
              copy[k][i] = copy[k][j];
              copy[k][j] = tmp;
            }
          }
        }
      }
    } else { // descending
      for (int i = 0; i < rows - 1; i++) {
        for (int j = i + 1; j < rows; j++) {
          if (copy[column][i].compareTo(copy[column][j]) < 0) {
            for (int k = 0; k < columns; k++) {
              String tmp = copy[k][i];
              copy[k][i] = copy[k][j];
              copy[k][j] = tmp;
            }
          }
        }
      }
    }
    csv = copy;
  }
}